home *** CD-ROM | disk | FTP | other *** search
- /*
- ** Apple Macintosh Developer Technical Support
- **
- ** Routines demonstrating how to record sound asynchronously to disk.
- **
- ** by Mark Cookson, Apple Developer Technical Support
- **
- ** File: Record_sound.c
- **
- ** Copyright ©1996 Apple Computer, Inc.
- ** All rights reserved.
- **
- ** You may incorporate this sample code into your applications without
- ** restriction, though the sample code has been provided "AS IS" and the
- ** responsibility for its operation is 100% yours. However, what you are
- ** not permitted to do is to redistribute the source as "Apple Sample
- ** Code" after having made changes. If you're going to re-distribute the
- ** source, we require that you make it clear in the source that the code
- ** was descended from Apple Sample Code, but that you've made changes.
- */
-
- /*
- This sample shows how to use SPBRecord to record to memory and then
- write the recorded samples to disk, asynchronously, using PBWriteAsync.
-
- This sample is useful for those developers who wish more flexibility
- than what is offered with SPBRecordToFile.
-
- This sample uses two parameter blocks because as Macs get faster, and
- drives can't quite keep up, using just one parameter block for two writes
- is just asking for trouble. It's not that much extra memory and it will
- be a weird bug you never have to find. The pbInUse field of each parameter
- block is supposed to keep us from reusing the parameter block in case things
- get really stopped up.
-
- These routines are designed to be easliy portable (in fact they were ripped
- from another sample), so you should be able to use them with little modification.
-
- It checks for errors returned from SPBRecord and from the PBWrites. If there
- is a disk error returned from a PBWrite the SPBRecord completion routine will
- not start the next record, killing the recording process and the error will be
- returned in the Vars structure (in the theErr field).
-
- This sample records three seconds of sound but doesn't call WaitNextEvent, so
- don't worry, your Mac isn't locked up, just wait a few seconds.
-
- Happy recording!
- */
-
- #include <Quickdraw.h>
- #include <Windows.h>
- #include <dialogs.h>
- #include <OSEvents.h>
- #include <Memory.h>
- #include <Packages.h>
- #include <Sound.h>
- #include <SoundInput.h>
- #include <OSUtils.h>
- #include <Files.h>
- #include <StandardFile.h>
- #include <Fonts.h>
-
- //So we can pass our A5 and other info to our PBWrite completion routines
- typedef struct myParamBlockRec {
- ParamBlockRec pb;
- long myA5;
- Boolean pbInUse;
- }myParamBlockRec, *myParmBlkPtr;
-
- //Keep track of the info needed to record
- typedef struct {
- long sanitycheck;
- SPBPtr recordRec;
- myParmBlkPtr pb0,
- pb1;
- Ptr recBuffer0,
- recBuffer1;
- Fixed sampleRate;
- OSType compression;
- unsigned long totalBytes;
- long myA5,
- devBuffer,
- soundRefNum;
- short whichBuffer,
- fileRefNum,
- numChannels,
- sampleSize;
- OSErr theErr; //last error returned by SPBRecord or PBWrite
- } Vars, *VarsPtr;
-
- OSErr PrepairToRecordToDisk (VarsPtr myVars);
- OSErr RecordToDisk (VarsPtr myVars);
- pascal void MyRecComp (SPBPtr inParamPtr);
- OSErr FinishRecording (VarsPtr myVars);
-
- #if GENERATINGCFM
- pascal void MyPB0WriteComp (myParmBlkPtr passedPB);
- pascal void MyPB1WriteComp (myParmBlkPtr passedPB);
- #else
- pascal void MyPB0WriteComp (myParmBlkPtr passedPB:__a0);
- pascal void MyPB1WriteComp (myParmBlkPtr passedPB:__a0);
- #endif
-
- static void ToolBoxInit (void)
- {
- MaxApplZone();
- InitGraf (&qd.thePort);
- InitFonts ();
- InitWindows ();
- InitMenus ();
- TEInit ();
- InitDialogs ((long)nil);
- InitCursor ();
- return;
- }
-
- /*
- void main (void) {
- Vars myVars;
- long temp;
- OSErr err;
-
- ToolBoxInit ();
-
- err = PrepairToRecordToDisk (&myVars);
- err = RecordToDisk (&myVars);
-
- Delay (180, &temp); //record for about three seconds
-
- err = myVars.theErr; //were any errors returned from the recording?
- if (err != noErr && err != abortErr) {
- DebugStr ("\pRecording didn't complete without error");
- }
-
- //if the error was that we ran out of disk space we can still write the
- //header because we preallocated space for it. The file may be bad,
- //but it doesn't have to be. At any rate we want to close the file and
- //dispose of memory.
- err = FinishRecording (&myVars);
- }
- */
-
- static OSErr SetupDevice (long inputDevice,
- short *numChannels,
- short *sampleSize,
- Fixed *sampleRate,
- OSType *compression,
- long *devBuffer) {
-
- OSErr err;
- Fixed gain = 0x00008000;
- short on = 1;
-
- err = SPBSetDeviceInfo (inputDevice, siSampleRate, (Ptr) sampleRate);
- if (err != noErr)
- DebugStr("\pcouldn't set sample rate");
-
- err = SPBSetDeviceInfo (inputDevice, siSampleSize, (Ptr) sampleSize);
- if (err != noErr)
- DebugStr("\pcouldn't set sample size");
-
- err = SPBSetDeviceInfo (inputDevice, siTwosComplementOnOff, (Ptr) &on);
- if (err != noErr)
- DebugStr("\pcouldn't set twos complement");
-
- err = SPBSetDeviceInfo (inputDevice, siNumberChannels, (Ptr) numChannels);
- if (err != noErr)
- DebugStr("\pcouldn't set number of channels");
-
- err = SPBSetDeviceInfo (inputDevice, siCompressionType, (Ptr) compression);
- if (err != noErr)
- DebugStr("\pcouldn't set compression type");
-
- //turn on continuous recording to "warm up" the input device
- err = SPBSetDeviceInfo (inputDevice, siContinuous, (Ptr) &on);
- if (err != noErr)
- DebugStr("\pcouldn't turn on continuous recording");
-
- //turn on Automatic Gain Control
- err = SPBSetDeviceInfo (inputDevice, siAGCOnOff, (Ptr) &on);
- if (err != noErr) {
- //If AGC isn't available, just turn it all the way down to avoid over driving
- err = SPBSetDeviceInfo (inputDevice, siInputGain, (Ptr) &gain);
- if (err != noErr)
- DebugStr("\pcouldn't get siInputGain");
- }
-
- //check to see what we really got
- err = SPBGetDeviceInfo (inputDevice, siSampleRate, (Ptr) sampleRate);
- if (err != noErr)
- DebugStr("\pcouldn't get sample rate");
-
- err = SPBGetDeviceInfo (inputDevice, siSampleSize, (Ptr) sampleSize);
- if (err != noErr)
- DebugStr("\pcouldn't get sample size");
-
- err = SPBGetDeviceInfo (inputDevice, siNumberChannels, (Ptr) numChannels);
- if (err != noErr)
- DebugStr("\pcouldn't get number of channels");
-
- err = SPBGetDeviceInfo (inputDevice, siDeviceBufferInfo, (Ptr) devBuffer);
- if (err != noErr)
- DebugStr("\pcouldn't get number of channels");
-
- err = SPBGetDeviceInfo (inputDevice, siCompressionType, (Ptr) compression);
- if (err != noErr)
- DebugStr("\pcouldn't get compression type");
-
- return err;
- }
-
- OSErr PrepairToRecordToDisk (VarsPtr myVars) {
- OSErr err;
- short volRefNum;
- long buffSize, parID;
- StandardFileReply sfReply;
- StringPtr nameString;
-
- StandardPutFile ("\pPut recorded AIFF where...", "\pRecorded sound", &sfReply);
- if (sfReply.sfGood) {
- volRefNum = sfReply.sfFile.vRefNum;
- parID = sfReply.sfFile.parID;
- nameString = (StringPtr)&sfReply.sfFile.name;
-
- err = SPBOpenDevice (nil, siWritePermission, &myVars->soundRefNum);
- if (err != noErr) {
- DebugStr("\pcouldn't open the device");
- }
-
- myVars->numChannels = 2;
- myVars->sampleSize = 16;
- myVars->sampleRate = rate44khz;
- myVars->compression = 'NONE';
- err = SetupDevice (myVars->soundRefNum, &myVars->numChannels,
- &myVars->sampleSize,
- &myVars->sampleRate,
- &myVars->compression,
- &myVars->devBuffer);
-
- buffSize = myVars->devBuffer * 20; //make our buffer a multiple of the hardware's
- myVars->recBuffer0 = NewPtrClear(buffSize);
- if (MemError() != noErr || myVars->recBuffer0 == nil) {
- DebugStr("\pcouldn't get memory for recording buffer");
- err = memFullErr;
- }
-
- myVars->recBuffer1 = NewPtrClear(buffSize);
- if (MemError() != noErr || myVars->recBuffer1 == nil) {
- DebugStr("\pcouldn't get memory for recording buffer");
- err = memFullErr;
- }
-
- if (err == noErr) {
- myVars->recordRec = (SPBPtr) NewPtrClear(sizeof (SPB));
- if (MemError() != noErr || myVars->recordRec == nil) {
- DebugStr("\pcouldn't get memory for recording record");
- err = memFullErr;
- }
- }
-
- if (err == noErr) {
- myVars->pb0 = (myParmBlkPtr) NewPtrClear(sizeof (myParamBlockRec));
- if (MemError() != noErr || myVars->pb0 == nil) {
- DebugStr("\pcouldn't get memory for param block");
- err = memFullErr;
- }
- }
-
- if (err == noErr) {
- myVars->pb1 = (myParmBlkPtr) NewPtrClear(sizeof (myParamBlockRec));
- if (MemError() != noErr || myVars->pb1 == nil) {
- DebugStr("\pcouldn't get memory for param block");
- err = memFullErr;
- }
- }
-
- if (err == noErr) {
- HParamBlockRec Hpb;
- IOCompletionUPP MyPB0WriteCompUPP,
- MyPB1WriteCompUPP;
- SICompletionUPP MyRecCompUPP;
-
- myVars->sanitycheck = 'SANE';
- myVars->myA5 = SetCurrentA5 ();
- myVars->whichBuffer = 0;
- myVars->pb0->myA5 = SetCurrentA5 ();
- myVars->pb1->myA5 = SetCurrentA5 ();
-
- //set up the record parameters
- MyRecCompUPP = NewSICompletionProc (MyRecComp);
- myVars->recordRec->inRefNum = myVars->soundRefNum;
- myVars->recordRec->count = buffSize;
- myVars->recordRec->milliseconds = 0;
- myVars->recordRec->bufferLength = buffSize;
- myVars->recordRec->bufferPtr = myVars->recBuffer0;
- myVars->recordRec->completionRoutine = MyRecCompUPP;
- myVars->recordRec->interruptRoutine = nil;
- myVars->recordRec->userLong = (long)myVars;
- myVars->recordRec->error = 0;
- myVars->recordRec->unused1 = 0;
-
- //create the file
- Hpb.ioParam.ioCompletion = nil;
- Hpb.ioParam.ioNamePtr = nameString;
- Hpb.ioParam.ioVRefNum = volRefNum;
- Hpb.fileParam.ioDirID = parID;
- err = PBHCreateSync (&Hpb);
- if (err == dupFNErr) {
- err = noErr; //overwriting the file is not an error
- }
- if (err != noErr)
- DebugStr("\pPBHCreateSync failed");
-
- if (err == noErr) {
- //set the file type and creator
- Hpb.fileParam.ioVRefNum = volRefNum;
- Hpb.fileParam.ioDirID = parID;
- Hpb.fileParam.ioNamePtr = nameString;
- Hpb.fileParam.ioFVersNum = 0;
- Hpb.fileParam.ioFDirIndex = 0;
- err = PBHGetFInfoSync(&Hpb);
- if (err != noErr)
- DebugStr("\pPBHGetFInfoSync failed");
- }
- if (err == noErr) {
- Hpb.fileParam.ioFlFndrInfo.fdType = 'AIFF';
- Hpb.fileParam.ioFlFndrInfo.fdCreator = 'Csdo';
- Hpb.fileParam.ioDirID = parID;
- err = PBHSetFInfoSync (&Hpb);
- if (err != noErr)
- DebugStr("\pPBHSetFInfoSync failed");
- }
-
- if (err == noErr) {
- //open the file for writing
- Hpb.ioParam.ioCompletion = nil;
- Hpb.ioParam.ioNamePtr = nameString;
- Hpb.ioParam.ioVRefNum = volRefNum;
- Hpb.ioParam.ioPermssn = fsRdWrPerm;
- Hpb.fileParam.ioDirID = parID;
- err = PBHOpenDFSync (&Hpb);
- if (err != noErr)
- DebugStr("\pPBHOpenDFSync failed");
- }
-
- if (err == noErr) {
- myVars->fileRefNum = Hpb.ioParam.ioRefNum;
-
- //set up the parameter blocks for the coming writes
- MyPB0WriteCompUPP = NewIOCompletionProc (MyPB0WriteComp);
- MyPB1WriteCompUPP = NewIOCompletionProc (MyPB1WriteComp);
- myVars->pb0->pb.ioParam.ioCompletion = MyPB0WriteCompUPP;
- myVars->pb0->pb.ioParam.ioVRefNum = volRefNum;
- myVars->pb0->pb.ioParam.ioRefNum = myVars->fileRefNum;
- myVars->pb0->pb.ioParam.ioPosMode = fsAtMark;
-
- myVars->pb1->pb.ioParam.ioCompletion = MyPB1WriteCompUPP;
- myVars->pb1->pb.ioParam.ioVRefNum = volRefNum;
- myVars->pb1->pb.ioParam.ioRefNum = myVars->fileRefNum;
- myVars->pb1->pb.ioParam.ioPosMode = fsAtMark;
-
- //write a temp AIFF header so file pointer for data is in the right place
- err = SetupAIFFHeader (myVars->fileRefNum, myVars->numChannels, myVars->sampleRate, myVars->sampleSize, myVars->compression, 0, 0);
- if (err != noErr)
- DebugStr("\pSetupAIFFHeader failed");
- }
- }
- } else {
- err = userCanceledErr;
- }
-
- return err;
- }
-
- OSErr RecordToDisk (VarsPtr myVars) {
- OSErr err = noErr;
-
- myVars->totalBytes = 0;
- err = SPBRecord (myVars->recordRec, true);
- if (err != noErr) {
- DebugStr("\pSPBRecord failed");
- }
-
- return err;
- }
-
- /*
- Stops the recording by calling SPBStopRecording and then writes the
- correct file header to the file, closes the file and then disposes of
- our pointers.
- */
- OSErr FinishRecording (VarsPtr myVars) {
- OSErr err;
- ParamBlockRec pb;
-
- err = SPBStopRecording (myVars->soundRefNum);
- if (err != noErr)
- DebugStr("\pSPBStopRecording failed");
-
- err = SPBCloseDevice (myVars->soundRefNum);
- if (err != noErr)
- DebugStr("\pSPBCloseDevice failed");
-
- //Put the file pointer back to the start of the file to
- //write the correct header over the temp header
- pb.ioParam.ioCompletion = nil;
- pb.ioParam.ioRefNum = myVars->fileRefNum;
- pb.ioParam.ioPosMode = fsFromStart;
- pb.ioParam.ioPosOffset = 0;
- err = PBSetFPosSync (&pb);
- if (err != noErr)
- DebugStr("\pPBSetFPosSync failed");
-
- if (err == noErr) {
- //write the header with the correct information
- err = SetupAIFFHeader (myVars->fileRefNum, myVars->numChannels, myVars->sampleRate, myVars->sampleSize, myVars->compression, myVars->totalBytes, myVars->totalBytes/(myVars->sampleSize*myVars->numChannels));
- if (err != noErr)
- DebugStr("\pSetupAIFFHeader in FinishRecording failed");
- }
-
- err = PBCloseSync (&pb);
- if (err != noErr)
- DebugStr("\pPBCloseSync failed");
-
- DisposePtr ((Ptr)myVars->recordRec);
- DisposePtr (myVars->recBuffer0);
- DisposePtr (myVars->recBuffer1);
- DisposePtr ((Ptr)myVars->pb0);
- DisposePtr ((Ptr)myVars->pb1);
-
- return err;
- }
-
- /*
- This gets called at the end of each record, it writes the recorded data to
- disk (asynchronously) and then starts a new record using the same SPBPtr.
-
- Make sure you have continous recording turned on otherwise the recording
- may loose data between calls to SPBRecord.
- */
- pascal void MyRecComp (SPBPtr inParamPtr) {
- VarsPtr myVarsPtr;
- OSErr err;
-
- #if !GENERATINGCFM
- long oldA5;
- oldA5 = SetA5 (((VarsPtr)inParamPtr->userLong)->myA5);
- #endif
-
- myVarsPtr = (VarsPtr)inParamPtr->userLong;
-
- //setting this now will avoid a race condition of the record finishing before
- //we can toggle which buffer to use
- myVarsPtr->whichBuffer = !myVarsPtr->whichBuffer;
-
- //If the last write returned with no error then continue recording
- if (myVarsPtr->pb0->pb.ioParam.ioResult == noErr && myVarsPtr->pb1->pb.ioParam.ioResult == noErr) {
- //If we are aborting (stopping) the recording, we still want to write the last buffer
- if (inParamPtr->error == noErr || inParamPtr->error == abortErr) {
- if (myVarsPtr->whichBuffer == 1) {
- myVarsPtr->pb0->pb.ioParam.ioBuffer = myVarsPtr->recBuffer0;
- myVarsPtr->pb0->pb.ioParam.ioReqCount = myVarsPtr->recordRec->count;
- myVarsPtr->totalBytes += myVarsPtr->recordRec->count;
- if (myVarsPtr->pb0->pbInUse == false) {
- myVarsPtr->pb0->pbInUse = true;
- err = PBWriteAsync (&myVarsPtr->pb0->pb);
- }
- inParamPtr->bufferPtr = myVarsPtr->recBuffer1;
- if (inParamPtr->error == noErr) {
- err = SPBRecord (inParamPtr, true);
- if (err != noErr)
- DebugStr("\pSPBRecord1 failed");
- }
- } else {
- myVarsPtr->pb1->pb.ioParam.ioBuffer = myVarsPtr->recBuffer1;
- myVarsPtr->pb1->pb.ioParam.ioReqCount = myVarsPtr->recordRec->count;
- myVarsPtr->totalBytes += myVarsPtr->recordRec->count;
- if (myVarsPtr->pb1->pbInUse == false) {
- myVarsPtr->pb1->pbInUse = true;
- err = PBWriteAsync (&myVarsPtr->pb1->pb);
- }
- inParamPtr->bufferPtr = myVarsPtr->recBuffer0;
- if (inParamPtr->error == noErr) {
- err = SPBRecord (inParamPtr, true);
- if (err != noErr)
- DebugStr("\pSPBRecord2 failed");
- }
- }
- myVarsPtr->theErr = err;
- }
- } else {
- //There was an error from PBWrite, return the error in theErr of our structure
- if (myVarsPtr->pb0->pb.ioParam.ioResult != noErr)
- myVarsPtr->theErr = myVarsPtr->pb0->pb.ioParam.ioResult;
- else myVarsPtr->theErr = myVarsPtr->pb1->pb.ioParam.ioResult;
- }
-
- #if !GENERATINGCFM
- oldA5 = SetA5 (oldA5);
- #endif
-
- return;
- }
-
- /*
- These routines are here just for error checking in this example,
- you may wish to update a status record or something similar.
-
- What do you do if you get a write error during async recording?
- For this example we will simply terminate the recording process,
- you should probably display an error. Since the myParamBlockRec
- doesn't contain the sound input device number we will check for
- an error in the record completion routine and not start a new
- recording if there was an error. You can call SPBStopRecording
- at interrupt time, but that isn't needed in this example.
-
- These routines display the error, but the record completion
- routine checks the parameter blocks to make sure there was no
- error before continuing the recording.
- */
- #if GENERATINGCFM
- pascal void MyPB0WriteComp (myParmBlkPtr passedPB)
- #else
- pascal void MyPB0WriteComp (myParmBlkPtr passedPB:__a0)
- #endif
- {
- if (passedPB->pb.ioParam.ioResult != noErr) {
- DebugStr("\pPBWrite0 failed!");
- }
-
- passedPB->pbInUse = false;
- }
-
- #if GENERATINGCFM
- pascal void MyPB1WriteComp (myParmBlkPtr passedPB)
- #else
- pascal void MyPB1WriteComp (myParmBlkPtr passedPB:__a0)
- #endif
- {
- if (passedPB->pb.ioParam.ioResult != noErr) {
- DebugStr("\pPBWrite1 failed!");
- }
-
- passedPB->pbInUse = false;
- }
-
-